package com.oristartech.ori.common.generater;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import freemarker.template.Configuration;
import freemarker.template.TemplateException;

public abstract class DBGeneraterAbstract {

    private final Logger log = LoggerFactory.getLogger(this.getClass());

    // 获取数据IP地址
    public abstract String getDatabaseIPAddress();

    // 获取数据库名
    public abstract String getDatabaseName();

    // 获取数据库用户名
    public abstract String getDatabaseUserName();

    // 获取数据库密码
    public abstract String getDatabasePassword();

    // 获取租户ID，即表后缀
    public abstract String getTableTenantId();

    // 获取影院ID，即表后缀
    public abstract String getTableCinemaId();

    // 获取项目工程根目录
    public abstract String getProjectRootDirectory();

    // 获取项目工程实体类目录
    public abstract String getProjectEntityDirectory();

    // 获取项目工程基础包名
    public abstract String getProjectBasePackageName();

    // 获取要生成的表名或表名前缀
    public abstract String[] getGenerateTableNameOrPrefix();

    // 获取模板目录
    public abstract String getFreemarkTemplateDirectory();

    // 是否重新生成Mapper
    public abstract boolean isGenerateMapper();

    // 是否重新生成MapperXml
    public abstract boolean isGenerateMapperXml();

    // 是否重新生成BaseMapper
    public abstract boolean isGenerateBaseMapper();

    // 是否重新生成BaseMapperXml
    public abstract boolean isGenerateBaseMapperXml();

    // 是否重新生成Entity
    public abstract boolean isGenerateEntity();

    // 是否重新生成Service
    public abstract boolean isGenerateService();

    // 是否重新生成ServiceImpl
    public abstract boolean isGenerateServiceImpl();

    // 是否重新生成Facade
    public abstract boolean isGenerateFacade();

    // 是否重新生成FacadeImpl
    public abstract boolean isGenerateFacadeImpl();

    // freemarker配置信息
    private Configuration configuration = null;
    // 生成数据库表列表
    private List<Table> tableList = new ArrayList<>();

    /**
     * 生成
     * 
     * @throws SQLException
     * @throws ClassNotFoundException
     */
    public void generate() throws SQLException, ClassNotFoundException {
        List<Table> list = getAllTableData();
        for (Table table : list) {
            generateMapperXml(table, isGenerateMapperXml());
            generateBaseMapperXml(table, isGenerateBaseMapperXml());
            generateMapper(table, isGenerateMapper());
            generateBaseMapper(table, isGenerateBaseMapper());
            generateEntity(table, isGenerateEntity());
            generateService(table, isGenerateService());
            generateServiceImpl(table, isGenerateServiceImpl());
            generateFacade(table, isGenerateFacade());
            generateFacadeImpl(table, isGenerateFacadeImpl());
        }
        log.info("generate 共 {} 个", list.size());
        log.info("generate done.");
    }

    public List<Table> getTableList() {
        return tableList;
    }

    private Configuration getConfiguration() {
        if (configuration != null) {
            return configuration;
        }
        try {
            configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
            configuration.setDefaultEncoding("UTF-8");
            configuration.setDirectoryForTemplateLoading(new File(getFreemarkTemplateDirectory()));
        } catch (IOException e) {
            log.error("", e);
        }
        return configuration;
    }

    // 获取要忽略的表后缀，租户ID、影院ID
    private String[] getGenerateIgnoreTableSuffix() {
        List<String> tableSuffix = new ArrayList<>();
        if (!StringUtils.isBlank(getTableTenantId())) {
            tableSuffix.add("_".concat(getTableTenantId()));
        }
        if (!StringUtils.isBlank(getTableCinemaId())) {
            tableSuffix.add("_".concat(getTableCinemaId()));
        }
        return tableSuffix.toArray(new String[tableSuffix.size()]);
    }

    private void generateFacadeImpl(Table table, boolean isCover) {
        String fileName = table.getJavaModuleDirectory(getProjectRootDirectory());
        fileName += "facade/impl/";
        fileName += table.getJavaPascalName();
        fileName += "FacadeImpl.java";
        generateFile(table, fileName, "facadeImplTemplate.ftl", isCover);
    }

    private void generateFacade(Table table, boolean isCover) {
        String directory = getProjectEntityDirectory();
        if (directory == null || "".equals(directory)) {
            directory = getProjectRootDirectory();
        }
        String fileName = table.getJavaModuleDirectory(directory);
        fileName += "facade/";
        fileName += table.getJavaPascalName();
        fileName += "Facade.java";
        generateFile(table, fileName, "facadeTemplate.ftl", isCover);
    }

    private void generateServiceImpl(Table table, boolean isCover) {
        String fileName = table.getJavaModuleDirectory(getProjectRootDirectory());
        fileName += "service/impl/";
        fileName += table.getJavaPascalName();
        fileName += "ServiceImpl.java";
        generateFile(table, fileName, "serviceImplTemplate.ftl", isCover);
    }

    private void generateService(Table table, boolean isCover) {
        String fileName = table.getJavaModuleDirectory(getProjectRootDirectory());
        fileName += "service/";
        fileName += table.getJavaPascalName();
        fileName += "Service.java";
        generateFile(table, fileName, "serviceTemplate.ftl", isCover);
    }

    private void generateEntity(Table table, boolean isCover) {
        String directory = getProjectEntityDirectory();
        if (directory == null || "".equals(directory)) {
            directory = getProjectRootDirectory();
        }
        String fileName = table.getJavaModuleDirectory(directory);
        fileName += "entity/";
        fileName += table.getJavaPascalName();
        fileName += "Entity.java";
        generateFile(table, fileName, "entityTemplate.ftl", isCover);
    }

    private void generateBaseMapper(Table table, boolean isCover) {
        String fileName = table.getJavaModuleDirectory(getProjectRootDirectory());
        fileName += "mapper/base/Base";
        fileName += table.getJavaPascalName();
        fileName += "Mapper.java";
        generateFile(table, fileName, "baseMapperTemplate.ftl", isCover);
    }

    private void generateMapper(Table table, boolean isCover) {
        String fileName = table.getJavaModuleDirectory(getProjectRootDirectory());
        fileName += "mapper/";
        fileName += table.getJavaPascalName();
        fileName += "Mapper.java";
        generateFile(table, fileName, "mapperTemplate.ftl", isCover);
    }

    private void generateBaseMapperXml(Table table, boolean isCover) {
        String fileName = table.getJavaMapperXmlDirectory(getProjectRootDirectory());
        fileName += "base/Base";
        fileName += table.getJavaPascalName();
        fileName += "Mapper.xml";
        String baseMapperXmlTemplate = null;
        if (Table.DIRECTORY_POS_SHAREPOS.equalsIgnoreCase(getProjectBasePackageName())) {
            baseMapperXmlTemplate = "baseMapperXmlTemplateForSharepos.ftl";
        } else {
            baseMapperXmlTemplate = "baseMapperXmlTemplate.ftl";
        }
        generateFile(table, fileName, baseMapperXmlTemplate, isCover);
    }

    private void generateMapperXml(Table table, boolean isCover) {
        String fileName = table.getJavaMapperXmlDirectory(getProjectRootDirectory());
        fileName += table.getJavaPascalName();
        fileName += "Mapper.xml";
        generateFile(table, fileName, "mapperXmlTemplate.ftl", isCover);
    }

    private void generateFile(Table table, String fileName, String templte, boolean isCover) {
        File outFile = new File(fileName); // 生成文件的路径
        if (!isCover && outFile.exists()) {
            return;
        }
        File path = outFile.getParentFile();
        if (!path.exists()) {
            path.mkdirs();
        }
        try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile)))) {
            Configuration config = getConfiguration();
            config.getTemplate(templte).process(table, out);
        } catch (FileNotFoundException e) {
            log.error("FileNotFoundException", e);
        } catch (TemplateException e) {
            log.error("TemplateException", e);
        } catch (IOException e) {
            log.error("IOException", e);
        }
    }

    private boolean isGenerate(String tableName) {
        if (null == tableName) {
            return false;
        }
        StringBuilder tableSuffix = new StringBuilder();
        String cinemaId = getTableCinemaId();
        if (!StringUtils.isBlank(cinemaId)) {
            tableSuffix.append("_");
            tableSuffix.append(cinemaId);
        }
        String tenantId = getTableTenantId();
        if (!StringUtils.isBlank(tenantId)) {
            tableSuffix.append("_");
            tableSuffix.append(tenantId);
        }
        if (!tableName.trim().toLowerCase().endsWith(tableSuffix.toString().toLowerCase())) {
            return false;
        }
        return checkGenerateTable(tableName);
    }

    private boolean checkGenerateTable(String tableName) {
        boolean matchGenerateTable = true;
        String[] generateTable = getGenerateTableNameOrPrefix();
        if (generateTable == null || generateTable.length <= 0) {
            return matchGenerateTable;
        }
        matchGenerateTable = false;
        for (String name : generateTable) {
            if ((name != null && name.trim().endsWith("_")
                && tableName.trim().toLowerCase().startsWith(name.trim().toLowerCase()))
                || (name != null && !name.trim().endsWith("_") && tableName.trim().equalsIgnoreCase(name.trim()))) {
                matchGenerateTable = true;
                break;
            }
        }
        return matchGenerateTable;
    }

    private List<Table> getAllTableData() throws SQLException, ClassNotFoundException {
        tableList.clear();
        Connection conn = getConnection();
        DatabaseMetaData databaseMetaData = conn.getMetaData();
        ResultSet tableRs = databaseMetaData.getTables(getDatabaseName(), "%", "%", new String[] {"TABLE"});
        while (tableRs.next()) {
            Table table = new Table();
            String tableName = tableRs.getString("TABLE_NAME");
            if (!isGenerate(tableName)) {
                continue;
            }
            log.info("generate {}", tableName);
            table.setIgnoreTableSuffix(getGenerateIgnoreTableSuffix());
            table.setJavaBasePackageName(getProjectBasePackageName());
            table.setMysqlTableName(tableName);
            table.setMysqlTableComment(tableRs.getString("REMARKS"));
            bindTablePrimaryKeysData(databaseMetaData, table);
            bindTableColumnData(databaseMetaData, table);
            table.buildAll();
            tableList.add(table);
        }
        return tableList;
    }

    private void bindTablePrimaryKeysData(DatabaseMetaData databaseMetaData, Table table) throws SQLException {
        ResultSet rs = databaseMetaData.getPrimaryKeys(getDatabaseName(), null, table.getMysqlTableName());
        while (rs.next()) {
            String columnName = rs.getString("COLUMN_NAME");// 列名
            if (StringUtils.isNotBlank(columnName)) {
                table.setMysqlPrimaryKeysColumnName(columnName);
            }
        }
    }

    private void bindTableColumnData(DatabaseMetaData databaseMetaData, Table table) throws SQLException {
        ResultSet rs = databaseMetaData.getColumns(getDatabaseName(), "%", table.getMysqlTableName(), "%");
        while (rs.next()) {
            Column column = new Column();
            column.setMysqlColumnName(rs.getString("COLUMN_NAME"));
            column.setMysqlDataType(rs.getString("TYPE_NAME"));
            column.setMysqlColumnComment(rs.getString("REMARKS"));
            table.addColumn(column);
            if (StringUtils.isNotBlank(table.getMysqlPrimaryKeysColumnName())
                && table.getMysqlPrimaryKeysColumnName().equals(column.getMysqlColumnName())) {
                table.setMysqlPrimaryKeysDataType(column.getMysqlDataType());
                table.setMysqlPrimaryKeysJavaType(column.getJavaType());
            }
        }
    }

    private Connection getConnection() throws ClassNotFoundException, SQLException {
        Connection conn = null;
        String connectionURL = "jdbc:mysql://".concat(getDatabaseIPAddress()).concat("/").concat(getDatabaseName());
        Class.forName("com.mysql.cj.jdbc.Driver"); // classLoader,加载对应驱动
        conn = DriverManager.getConnection(connectionURL, getDatabaseUserName(), getDatabasePassword());
        return conn;
    }
}
